from pwscrypt import PWSCrypt
import machine
import uos, pyb, utime, usys, gc
from pyb import Timer
import firmwareupdate
import rtc_wake
import sx126x
from pyb import Pin, ExtInt, LED
from machine import SPI, I2C
from ina219 import INA219
from wind import WIND
from sht3x import SHT3X
from ms5611 import MS5611
from rain import RAIN
from pv import PV
from max44009 import MAX44009
from veml6075 import VEML6075
from ads1115 import ADS1115
from micropython import const
from getFirmware import OTA
import ver
import pwsutil

NODE_ID = const(0x01)
HOST_ID = const(0x00)

I2C_MAX_ERR = const(8)

_count_no = 0			# 发送包计数器
_time_count = 0			# 2秒累积计数器

ota = None
_ota_begin = 0
_ver = 0
_size = 0
_tag = None

# 打包
def _packData():
	global _count_no
	global _firmware

	data = bytearray(42)

	data[0] = 0x01			# CMD_DATA
	data[1] = _count_no

	_count_no = (_count_no + 1) & 0xFF

	# Mask
	data[2] = 0x11

	data[3] = (_temperature >> 8) & 0xFF
	data[4] = _temperature & 0xFF

	data[5] = (_humidity >> 8) & 0xFF
	data[6] = _humidity & 0xFF

	data[7] = (_wind_dir >> 8) & 0xFF
	data[8] = _wind_dir & 0xFF

	data[9] = (_wind_speed >> 8) & 0xFF
	data[10] = _wind_speed & 0xFF

	data[11] = (_pressure >> 8) & 0xFF
	data[12] = _pressure & 0xFF

	data[13] = _solar_radiation >> 8 & 0xFF
	data[14] = _solar_radiation & 0xFF

	data[15] = (_illumination >> 24) & 0xFF
	data[16] = (_illumination >> 16) & 0xFF
	data[17] = (_illumination >> 8) & 0xFF
	data[18] = _illumination & 0xFF

	data[19] = (_uv_index >> 8) & 0xFF
	data[20] = _uv_index & 0xFF

	data[21] = (_uva >> 8) & 0xFF
	data[22] = _uva & 0xFF

	data[23] = (_uvb >> 8) & 0xFF
	data[24] = _uvb & 0xFF

	data[25] = (_battery_voltage >> 8) & 0xFF
	data[26] = _battery_voltage & 0xFF

	data[27] = (_battery_current >> 8) & 0xFF
	data[28] = _battery_current & 0xFF

	data[29] = (_rain >> 24) & 0xFF
	data[30] = (_rain >> 16) & 0xFF
	data[31] = (_rain >> 8) & 0xFF
	data[32] = _rain & 0xFF

	data[33] = (_lightning_dist >> 8) & 0xFF
	data[34] = _lightning_dist & 0xFF

	data[35] = (_solar_battery >> 8) & 0xFF
	data[36] = _solar_battery & 0xFF

	data[37] = (ver.ver >> 24) & 0xFF
	data[38] = (ver.ver >> 16) & 0xFF
	data[39] = (ver.ver >> 8) & 0xFF
	data[40] = ver.ver & 0xFF

	# 升级状态
	data[41] = _firmware

	# TODO:找到合适方案复位
	if (_firmware > 1):
		_firmware = 0

	return data

def _sendData(buf):
	sx126x.Send(buf, 0xFFFF)

	# wait for TX done
	while True:
		utime.sleep_ms(1)
		irqStatus = sx126x.GetIrqStatus()
		if ((irqStatus & sx126x.IRQ_TX_DONE) == sx126x.IRQ_TX_DONE):
			break
	return

def _recvData():
	# sx126x.SetRx(3200, True)		# Timeout 50ms
	sx126x.SetRx(6400, True)		# Timeout 100ms
	ret = False
	_data = None

	# wait for RX Done	
	for _ in range(200):
		utime.sleep_ms(1)
		irqStatus = sx126x.GetIrqStatus()
		if ((irqStatus & sx126x.IRQ_RX_TX_TIMEOUT) == sx126x.IRQ_RX_TX_TIMEOUT):
			break

		if ((irqStatus & sx126x.IRQ_RX_DONE) == sx126x.IRQ_RX_DONE):
			recvData = sx126x.Recv()
			# 判断是否本机接收
			# 至少 1 + 1 + 8 + 16 + 2字节
			if (len(recvData) < 28):
				break

			if ((recvData[0] == NODE_ID) or (recvData[1] != NODE_ID)):
				break

			#解密
			data = crypt.decrypt(recvData)
			ret = data[0]
			if (ret):
				_data = data[1]
			break
	
	return ret, _data

def _updateFW(data):
	global ota
	global _firmware
	global _ver
	global _size
	global _tag

	print("Begin/continue update")
	# 进入升级流程（和常规不同，这里不会退出。除非多次超时）
	_ver = (data[3] << 24) + (data[4] << 16) + (data[5] << 8) + data[6]
	_size = (data[7] << 24) + (data[8] << 16) + (data[9] << 8) + data[10]
	_tag = data[11:27]
	print("ver=", _ver)
	print("size=", _size)

	if (_ver == ver.ver):
		_firmware = 2
		print("Same version")
		return

	if (ota == None):
		ota = OTA(_sendData, _recvData, crypt, 3)
		_ota_begin = _time_count

	_firmware = 1

	return

def _getFW():
	global _count_no
	global ota
	global _ver
	global _size
	global _tag

	ret = ota.getFirmware([_ver, _size, _tag], _count_no)
	_count_no = (_count_no + 1) & 0xFF
	if (ret[0]):
		if (ret[1]):		# last package
			print("ready")
			if (firmwareupdate.update('/data/firmware.bin', _tag)):
				print("reboot")
				pyb.hard_reset()
			else:
				print("update failed!")
				ota.stop()
				ota = None
				return False

	return ret[0]

def _confirmFW():
	_firmware = 0
	try:
		uos.unlink('/data/fault')
	except:
		pass

	try:
		uos.unlink('/data/running.bak')
	except:
		pass

	return

def _boot2flash():
	try:
		uos.unlink('/data/running')
		utime.sleep(1)
		pyb.hard_reset()
	except:
		pass
	return	

def _procData(data):
	pwsutil.writeBKSRAM(1, 1)

	_cmd = data[2]
	if (_cmd == 0x00):
		return

	if (_cmd == 0x01):
		# New firmware
		_updateFW(data)
		return

	if (_cmd == 0x03):
		# Confirm
		_confirmFW()
		return

	if (_cmd == 0x02):
		# Reboot
		pyb.hard_reset()
		return
	
	if (_cmd == 0x04):
		# 删除running文件，可以进入boot状态
		_boot2flash()
		return


# PWS业务数据
_temperature	= 0		# 单位：0.01度
_humidity		= 0		# 单位：0.01%
_wind_dir		= 0		# 单位：度
_wind_speed		= 0		# 单位：0.1m/s
_pressure		= 0		# 单位：0.1mbar
_solar_radiation= 0		# 单位：w/m2
_illumination	= 0		# 单位：lux
_uv_index		= 0		# 单位：0.01
_uva			= 0		# 单位：0.01
_uvb			= 0		# 单位：0.01
_rain			= 0		# 单位：0.1mm（距离上次上报后的雨量汇总）
_lightning_dist	= 0		# 0:表示没有雷电，其他值表示雷电发生距离(KM)

_battery_voltage= 0		# 单位：mV
_battery_current= 0		# 单位：0.1ma
_solar_battery	= 0		# 单位：mV

_firmware = 0				# 固件状态  0：初始状态（无意义），1：正在OTA下载， 2：已经是相同版本， 3：固件校验失败或其他错误

PowerPin = Pin('B10', Pin.OUT_PP)
# poweroff(让I2C总线彻底断电)，用两个LED来消耗电容的电
PowerPin.off()
LED(2).on()
LED(3).on()
utime.sleep(1)

# poweron
PowerPin.on()

utime.sleep_ms(100)

spi = SPI(2)
spi.init(baudrate=10000000)

sx126x.Init(spi, rst='A15', nss='B12', busy='B4')
sx126x.Begin(freq=434000000, power=22)

i2c = I2C(1, freq=100000)
# 传感器初始化
ina_err = 0
try:
	ina = INA219(i2c)
except:
	ina = None

th_err = 0
try:
	th = SHT3X(i2c)
except:
	th = None

ms_err = 0
try:
	ms = MS5611(i2c)
except:
	ms = None

il_err = 0
try:
	il = MAX44009(i2c)
except:
	il = None

uv_err = 0
try:
	uv = VEML6075(i2c)
except:
	uv = None

sr_err = 0
try:
	sr = ADS1115(i2c)
except:
	sr = None

rain = RAIN()
wind = WIND()

# UV传感器初始化（同时启动第一次测量，因为VEML6075测量时间大于100ms，所以每次都是取上次启动测试后的结果） 
# 太阳能辐射使用8SPS，需要125ms
if (sr != None):
	try:
		sr.measure()
	except:
		sr_err += 1
if (il != None):
	try:
		il.setAuto()
	except:
		il_err += 1

if (uv != None):
	try:
		uv.measure()
	except:
		uv_err += 1

pv = PV()


# callback = lambda e: LED(2).toggle()
def extint_cb(l):
	LED(2).off()

ExtInt(Pin('B3'), ExtInt.IRQ_RISING, Pin.PULL_NONE, extint_cb)

crypt = PWSCrypt()

try:
	while True:
		if (_firmware != 1):
			pyb.stop()
			utime.sleep_ms(1)

		if (rtc_wake.wake):
			rtc_wake.wake = False
			_time_count += 1

			LED(1).on()

			# 启动传感器数据测量

			if (ina_err >= I2C_MAX_ERR):
				ina = None
				ina_err = 0
				_battery_voltage = 0
				_battery_current = 0

			if (th_err >= I2C_MAX_ERR):
				th = None
				th_err = 0
				_temperature = 0
				_humidity = 0

			if (ms_err >= I2C_MAX_ERR):
				ms = None
				ms_err = 0
				_pressure = 0

			if (il_err >= I2C_MAX_ERR):
				il = None
				il_err = 0
				_illumination = 0

			if (uv_err >= I2C_MAX_ERR):
				uv = None
				uv_err = 0
				_uv_index = 0
				_uva = 0
				_uvb = 0

			if (sr_err >= I2C_MAX_ERR):
				sr = None
				sr_err = 0
				_solar_radiation = 0

			# 气压温度
			try:
				if (ms != None):
					ms.start_d2()
					utime.sleep_ms(10)
					ms.read_d2()
					ms.start_d1()
			except:
				ms_err += 1

			try:
				if (th != None):
					th.convert(clock_stretch=False)
			except:
				th_err += 1

			wind.readReq()

			utime.sleep_ms(15)

			# 按合适顺序读取传感器测量结果
			try:
				if (ina != None):
					_battery_voltage = ina.bus_voltage
					_battery_current = round(ina.current * 10)
					ina_err = 0
			except:
				ina_err += 1

			_rain = rain.getRain()
			_solar_battery = pv.getVoltage()
			wds = wind.readData()
			_wind_dir = wds[0]
			_wind_speed = wds[1]

			try:
				if (th != None):
					data = th.read_temp_humi()
					_temperature = round(data[0] * 100)
					_humidity = round(data[1] * 100)
					th_err = 0
			except:
				th_err += 1

			try:
				if (ms != None):
					ms.read_d1()
					_ms_data = ms.read_pressure()
					_pressure = round(_ms_data[0] * 10)
					ms_err = 0
			except:
				ms_err += 1

			try:
				if (il != None):
					_illumination = round(il.getLux())
					il_err = 0
			except:
				il_err += 1

			try:
				if (uv != None):
					uvdata =uv.getUV()
					_uva = round(uvdata[0] * 100)
					_uvb = round(uvdata[1] * 100)
					_uv_index = round(uvdata[2] * 100)
					uv_err = 0
			except:
				uv_err += 1

			try:
				if (sr != None):
					_solar_radiation = round(sr.getData())
					sr_err = 0
			except:
				sr_err += 1

			# UV和SR因为时间长，需要启动测量，以便下次可以直接读取
			try:
				if (uv != None):
					uv.measure()
			except:
				uv_err += 1

			try:
				if (sr != None):
					sr.measure()
			except:
				sr_err += 1

			# 打包发送
			data = _packData()
			buf = crypt.encrypt(data)

			for i in range(2):
				LED(2).on()
				_sendData(buf)

				# 等待确认（超时重发）
				LED(3).on()
				data = _recvData()
				LED(3).off()

				ret = data[0]
				if (ret):
					break
			
			if (ret):
				# 成功接收数据，进行处理
				_procData(data[1])
			
			LED(1).off()

			# 检查OTA持续时间，暂定不超过1个小时（3600 / 2）
			if ((ota != None) and ((_time_count - _ota_begin) > 1800)):
				print("OTA timeout")
				ota.stop()
				ota = None
				_firmware = 0

		# 判断是否需要或在进行OTA
		# 第一个版本（没有问题），调用/flash目录下的v1版本的默认OTA获取过程（也是固化的紧急升级流程）
		if ((ota != None) and (_firmware == 1)):
			if (not _getFW()):
				print("getFW failed!")
				_firmware = 0

		if pyb.Switch().value():
			break

		print(pyb.RTC().datetime())
		print(gc.mem_free())

except Exception as e:
	usys.print_exception(e)
	utime.sleep(2)
	pyb.hard_reset()


